#    Copyright 2012 New Mexico EPSCoR
#
#    This file is part of Teacher's P.E.T.
#
#    Teacher's P.E.T. is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Teacher's P.E.T. is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Teacher's P.E.T..  If not, see <http://www.gnu.org/licenses/>.

from django.db import models
from django.http import Http404
from django import forms
from tags.templatetags.sanitize import sanitize
from django.template.defaultfilters import slugify
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from django.core.files.storage import FileSystemStorage
from django.utils.translation import ugettext_lazy
from django.utils.html import escape
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
from django.conf import settings

from apps.course.models import Course
from apps.course.functions import course_reverse
from apps.gradebook.models import Gradeables, Graded, GradedEditForm, GradingSection, GradingSectionGrade
from apps.feeds import FeedItem, FeedItems
from apps.participants.models import CourseParticipant

import logging
import datetime
import os
import StringIO
import zipfile

logger = logging.getLogger('pet.apps.activities.models')

# Create your models here.
class ActivityTemplate(models.Model):
	"""
	ActivityTemplates are small bits of code
	that are shown when users are editing activities.
	The Templates can be copied by content creators into
	their activities.
	"""
	category = models.CharField(max_length=255)
	title = models.CharField(max_length=255)
	description = models.TextField(blank=True,null=True)
	example = models.TextField(blank=True,null=True)
	content = models.TextField()
	def __unicode__(self):
		if self.description:
			return "%s - %s" % (self.title, self.description)
		return self.title
	@staticmethod
	def find(id=None, keep_queryset=False):
		logger.find_fxn('id %s, keep_queryset %s',id,keep_queryset)
		Objects = ActivityTemplate.objects.all()
		if id: Objects = Objects.filter(id=id)
		if Objects:
			if len(Objects) == 1 and id and not keep_queryset: return Objects[0]
			if not keep_queryset: Objects = sorted(Objects, key=lambda item: "%s %s" % (item.category, item.title))
			return Objects
		return None
	@staticmethod
	def find_or_404(id=None, keep_queryset=False):
		Return = ActivityTemplate.find(id=id, keep_queryset=keep_queryset)
		if not Return:
			logger.http404("Unable to find activity content template with id %s",id,display_caller=True)
			raise Http404
		return Return
	class Meta:
		unique_together = ['title','description']
		ordering = ['category','title']
class ActivityTemplateForm(forms.ModelForm):
	def __init__(self, *args, **kwargs):
		super(ActivityTemplateForm,self).__init__(*args, **kwargs)
		self.fields['description'].widget.attrs['class'] = 'mceNoEditor'
	class Meta:
		model = ActivityTemplate

class Subactivity(models.Model):
	"""
	Subactivities register connections between activities
	by recording which activity is a parent activity and which
	is a subactivity. It also includes order information
	so that subactivities can be reordered as necessary. This
	essentially mimics Django's ManyToMany functionality.
	"""
	parent_activity = models.ForeignKey('Activity',related_name="subactivities")
	subactivity = models.ForeignKey('Activity',related_name="parent_activities")
	order_number = models.PositiveIntegerField()

	def save(self, *args, **kwargs):
		if not self.order_number:
			self.order_number = self.parent_activity.subactivities.count() + 1
		super(Subactivity,self).save(*args,**kwargs)
class SubactivityForm(forms.ModelForm):
	class Meta:
		model = Subactivity
		fields = ['order_number']

class Activity(Graded, FeedItem):
	"""
	An activity is a collection of questions and
	HTML content. The questions are used to collect
	responses from students (in the form of
	ActivityResponses).

	Activity Members:
	content - the HTML content to display for this activity

	order_number - the order of activities displayed on the main menu
	display_title - the title used to display this activity in the main menu
	main_menu - if this activity should appear as a header in the main menu
	is_public - if this activity can be viewed by the public (otherwise only
	logged in uers can view it)

	activity_type - ACTIVITY_TYPE options determine how info about these responses are stored
	activity_results_type - ACTIVITY_RESULTS_TYPE options determine who can see results
	randomize_questions - if the questions should be randomized during display of the activity
	allow_late_submissions - are responses accepted after the question is closed?
	submissions_allowed - how many times a student can repeat submissions

	start_date - when the activity appears on the website
	end_date - when the activity is not longer accessible on the website
	"""
	ACTIVITY_TYPE = (
		('NORMAL','Normal - Requires login, answers stored with user info'),
		('ANONYMOUS','Anonymous - Requires login, answers stored without user info'),
		('PUBLIC',"Public - Doesn't require login, answers stored without user info"),
	)
	ACTIVITY_RESULTS_TYPE = (
		('PRIVATE','Private - only instructors and assistants can access activity results'),
		('PROTECTED','Protected - only users associated with the course can access activity results'),
		('PUBLIC','Public - anyone can access activity results'),
	)
	content = models.TextField("HTML Content", blank=True, null=True)

	#For activities that are parts of collections
	order_number = models.PositiveIntegerField(blank=True,null=True,help_text="This is used to specify the order of items in the main menu. ")
	display_title = models.CharField("Menu Display Title",blank=True,null=True,max_length=255,help_text="A short title to use when referencing this activity in a menu")
	main_menu = models.BooleanField("Main Menu Display",default=False,help_text="If true, this activity will appear in the main menu. ")
	is_public = models.BooleanField("Public",default=False,help_text="If true, this activity can be viewed by non-logged-in users. ")
	is_shared = models.BooleanField("Shared",default=True,help_text="If true, this activity can be seen by other instructors to use in their courses")
	show_collection_menu = models.BooleanField("Show Collection Menu",default=True,help_text="If true, a menu with all the activities in this collection will be shown when accessing this or a subactivity")
	
	#Activity options
	activity_type = models.CharField("Type",max_length=10,choices=ACTIVITY_TYPE,default=ACTIVITY_TYPE[0][0])
	activity_results_type = models.CharField("Results",max_length=10,choices=ACTIVITY_RESULTS_TYPE,default=ACTIVITY_RESULTS_TYPE[0][0])
	allow_html = models.BooleanField("Allow HTML in Respones", default=True, help_text="If true, this will allow responses to contain HTML markup")
	randomize_questions = models.BooleanField("Randomize Questions",default=False,help_text="If true, this will randomize the order of questions when displaying the activity. ")
	allow_late_submissions = models.BooleanField("Allow Submission After Closed",default=True,
		help_text="""If True, allows submissions to be accepted after the end date.
					All submission always have a date and time submitted so you know
			if they are late, regardless of this option.""")
	submissions_allowed = models.PositiveIntegerField("Submissions Allowed",default=0,
		help_text="""Number of submissions allowed for this activity 
					(0 means use the default from site options)""")

	#Interactivity options
	start_date = models.DateTimeField("Start Date",default=datetime.datetime.now(),blank=True,null=True,help_text="""After this date & time,
					  this activity will be viewable. This is also used as the default start for
					  all activity questions. Left blank, the activity is unavailable to users until
					  changed.""")
	end_date = models.DateTimeField("End Date",blank=True,null=True,help_text="""After this date & time,
					this activity will no longer accept answers to questions. This is also
					used as the default end for all activity questions. Left blank, the
					activity remains available for as long as the course is active.""")
	last_updated = models.DateTimeField(auto_now=True)
	
	#This is for easy access to the graded sections of activities
	questions = generic.GenericRelation("ActivityQuestion",related_name="+")

	def __init__(self, *args, **kwargs):
		"""
		This is here so we can set auto_primary_section to False.
	
		auto_primary_section is used by Graded objects to automatically create a GradingSection. Since
		we are using ActivityQuestions to be our GradingSections, we don't want an automatic one created.
		"""
		kwargs['auto_primary_section'] = False
		super(Activity,self).__init__(*args,**kwargs)
	def __unicode__(self):
		return "%s - %s" % (self.category, self.title)
	
	def has_uploads(self):
		for question in self.questions.all():
			if question.type == "UPLOAD":
				for response in ActivityResponseFileUpload.objects.filter(section=question):
					if response.response: return True
		return False
	def has_started(self):
		return (self.start_date and self.start_date < datetime.datetime.now()) or self.start_date is None
	def has_ended(self):
		return (self.end_date is not None and self.end_date > datetime.datetime.now())
	def get_response(self, section, user):
		from functions import CreateActivityResponseSet
		ResponseSet = CreateActivityResponseSet(activity=self, question_id=section.section_number, user=user)
		for question, response in ResponseSet.items():
			if question.type == "UPLOAD":
				return "<a href='%s' target='_blank'>Download</a>" % course_reverse('apps.activities.views.download',course=self.course.split(',')[0],category=self.category_slug,title=self.title_slug,pk=response[0].pk)
			elif (question.type == "TEXT" or question.type == "CHAR") and len(response[0].get_response(question)) > 50:
				return """<div class="expansion_container"><span class="expansion_title"><span class="arrow_down"></span>Response</span><span class="expansion_content"><hr>%s<hr><div class="expansion_footer"><span class="arrow_up"></span></div></span></div>""" % response[0].get_response(question)
			else: return response[0].get_response(question)
		return "__None"
	def get_question(self, section):
		Question = ActivityQuestion.objects.filter(activity=self,pk=section.id)
		if Question: return Question[0]
		return None
	@staticmethod
	def get_grades(course, classname, category_slug=None, title_slug=None):
		Categories = None
		Objects = classname.find(course,keep_queryset=True)
		if Objects:
			Categories = {}
			if category_slug: Objects = Objects.filter(category_slug = category_slug)
			if title_slug: Objects = Objects.filter(title_slug = title_slug)
			for object in Objects:
				if object.questions.all():
					if not object.category in Categories: Categories[object.category] = {}
					Categories[object.category][object] = object.total_points
		return Categories
	def get_edit_url(self,course):
		return course_reverse('management',course,app='activities',command='edit',arguments='%s&%s'%(self.category_slug,self.title_slug))
	def get_parent_activities(self):
		if not self.parent_activities: return None
		return [parent_activity.parent_activity for parent_activity in sorted(self.parent_activities.all(),key=lambda item: item.order_number if item.order_number else 1000)]
	def get_link(self,course):
		return course_reverse('apps.activities.views.view',course,category=self.category_slug,title=self.title_slug)
	def get_subactivities(self):
		if not self.subactivities: return None
		return [subactivity.subactivity for subactivity in sorted(self.subactivities.all(),key=lambda item: item.order_number if item.order_number else 1000)]
	def get_content_and_id(self):
		return "%s&%s" % (ContentType.objects.get_for_model(self).id,self.id)
	def get_completed_info(self):
		"""
		Get the info on students that have completed this activity
		"""
		Completed = {}
		NotCompleted = {}
		TotalRequired = 0
		NumComplete = NumIncomplete = 0
		for ActivityView in ActivityViewed.objects.filter(activity=self):
			if ActivityView.completed:
				Completed[ActivityView.user] = ActivityView
				NumComplete += 1
			else:
				NotCompleted[ActivityView.user] = ActivityView
				NumIncomplete += 1
		for course in self.course.split(','):
			for participant in CourseParticipant.find(course,status="STUDENT",keep_queryset=True):
				TotalRequired += 1
				if not participant.user in NotCompleted and not participant.user in Completed:
					NotCompleted[participant.user] = {'times_viewed':0,'first_viewed':'Never','last_viewed':'Never'}
					NumIncomplete += 1
		CompletedList = []
		NotCompletedList = []
		for user in Completed:
			CompletedList.append({'user':user,'data':Completed[user]})
		CompletedList = sorted(CompletedList,key=lambda item: "%s %s" % (item['user'].last_name,item['user'].first_name))
		for user in NotCompleted:
			NotCompletedList.append({'user':user,'data':NotCompleted[user]})
		NotCompletedList = sorted(NotCompletedList,key=lambda item: "%s %s" % (item['user'].last_name,item['user'].first_name))
		PercentCompleted = 100.0*NumComplete/(TotalRequired if TotalRequired else 1) if len(Completed)+len(NotCompleted) > 0 else 0
		return {'percent':PercentCompleted,'num_complete':NumComplete,'num_incomplete':NumIncomplete,'completed':CompletedList,'notcompleted':NotCompletedList}
	@staticmethod
	def find(course = None, activity_type = None, title_slug = None, category_slug = None, start_date = None, end_date = None, time = "PRESENT", is_public=None, keep_queryset=False):
		logger.find_fxn("course %s, activity_type %s, title_slug %s, category_slug %s, start_date %s, end_date %s, time %s, is_public %s, keep_queryset %s",
			 course, activity_type, title_slug, category_slug, start_date, end_date, time, is_public, keep_queryset)
		Objects = Activity.objects.all()
		if course: Objects = Objects.filter(course__contains=course)
		if activity_type: Objects = Objects.filter(activity_type=activity_type)
		if title_slug: Objects = Objects.filter(title_slug=title_slug)
		if category_slug: Objects = Objects.filter(category_slug=category_slug)
		if is_public is not None: Objects = Objects.filter(is_public=is_public)
		if time == "INTERACTIVE": Objects = Objects.filter(is_interactive = True)
		if start_date:
			if time=="FUTURE": Objects = Objects.filter(start_date__gt=start_date);
			else: Objects = Objects.filter(start_date__lte=start_date);
		if end_date:
			if time=="PAST": Objects = Objects.filter(end_date__lt=end_date);
			else: Objects = Objects.filter(end_date__gt=end_date);
		if Objects:
			if len(Objects) == 1 and title_slug and not keep_queryset: return Objects[0]
			if not keep_queryset: Objects = sorted(Objects, key=lambda item: "%s %s %s" % (item.activity_type, item.category, item.title))
			return Objects
		return None
	@staticmethod
	def find_or_404(course, activity_type = None, title_slug = None, category_slug = None, start_date = None, end_date = None, time = "PRESENT", keep_queryset=False):
		Return = Activity.find(course=course, activity_type=activity_type, title_slug=title_slug, category_slug=category_slug,
			   start_date=start_date, end_date=end_date, time=time, keep_queryset=keep_queryset)
		if not Return:
			logger.http404("Unable to find activity with course %s, activity_type %s, title %s, category %s, start_date %s, end_date %s, time %s",course,activity_type,title,category,start_date,end_date,time,display_caller=True)
			raise Http404
		return Return
	def get_appearance_date(self):
		return self.start_date
	def get_title(self):
		return self.title
	def get_description(self):
		return ''
FeedItems.register_item("Activity",Activity)
class ActivityEditForm(GradedEditForm):
	"""
	This form include a subactivities selector to select any subactivities. The extra functions are related to this functionality.
	"""
	subactivities = forms.ModelMultipleChoiceField(queryset=Activity.objects.all(),required=False,help_text='If this is a collection of activities, select the subactivities here. Hold down "Control", or "Command" on a Mac, to select more than one or to deselect one.')
	
	def __init__(self, *args, **kwargs):
		"""
		Checks if instance is supplied in kwargs. If so, it uses it to set initial values for subactivities and remove the option
		of selecting the instance or any of its parents as a subactivity.
		"""
		Subactivities = []
		if 'instance' in kwargs and kwargs['instance']:
			SubactivityObjects = Subactivity.objects.filter(parent_activity=kwargs['instance'])
			for activity in SubactivityObjects:
				Subactivities.append(activity.subactivity)
				if 'initial' in kwargs:
					kwargs['initial']['subactivities'] = Subactivities
				else:
					kwargs['initial'] = {'subactivities':Subactivities}
		super(ActivityEditForm,self).__init__(*args, **kwargs)
		self.fields['subactivities'].queryset = self.fields['subactivities'].queryset.exclude(id=self.instance.id)
		if 'course' in kwargs and kwargs['course']:
			self.fields['subactivities'].queryset = self.fields['subactivities'].queryset.filter(course__contains=kwargs['course'])
		SubactivityObjects = Subactivity.objects.filter(subactivity=kwargs['instance'])
		for subactivity in SubactivityObjects:
			self.fields['subactivities'].queryset = self.fields['subactivities'].queryset.exclude(id=subactivity.parent_activity.id)
		if self.fields['subactivities'].queryset.count() >= 10: self.fields['subactivities'].widget.attrs['size'] = 10
		else: self.fields['subactivities'].widget.attrs['size'] = self.fields['subactivities'].queryset.count()

	def clean_subactivities(self):
		"""
		Take whatever was entered in subactivities and synchronize Subactivity objects
		with the input. Also does some checking to make sure that the instance or
		any of its parents haven't been selected as subactivities.
		"""
		subactivities = self.cleaned_data.get("subactivities")
		ParentObjects = Subactivity.objects.filter(subactivity=self.instance)
		#Make sure that we haven't selected any of our parents as subactivities
		for activity in ParentObjects:
			if activity in subactivities: subactivities.remove(activity)
		SubactivityObjects = Subactivity.objects.filter(parent_activity=self.instance)
		num_subs = len(SubactivityObjects)
		#Delete any current subactivity objects that don't match our new list
		for activity in SubactivityObjects:
			if not activity in subactivities: activity.delete()
		for activity in subactivities:
			#Make sure we haven't selected ourself as a subactivity
			if activity == self.instance: continue
			#Make a new subactivity object for everything remaining in our subactivity list
			if not activity in SubactivityObjects:
				sub = Subactivity(parent_activity=self.instance,subactivity=activity,order_number=num_subs+1)
				sub.save()
				num_subs += 1
	class Meta(GradedEditForm.Meta):
		model = Activity
Gradeables.register_interface("Activities",Activity)

class ActivityViewed(models.Model):
	"""
	This class records when a user views an Activity, showing the
	date and time of the first view, the date and time of the
	last view, and the number of times viewed. If the user
	completes the activity, it will also record that and
	when the user first completed the activity.
	"""
	first_viewed = models.DateTimeField(auto_now_add=True)
	last_viewed = models.DateTimeField(auto_now=True)
	times_viewed = models.PositiveIntegerField(default=0)
	activity = models.ForeignKey(Activity)
	user = models.ForeignKey(User)
	completed = models.DateTimeField(blank=True,null=True)

	def __unicode__(self):
		return "%s - %s" % (self.activity, self.user)
	def save(self, *args, **kwargs):
		self.times_viewed = self.times_viewed + 1
		super(ActivityViewed,self).save(*args,**kwargs)

	class Meta:
		unique_together = ('activity','user')


class ActivityQuestion(GradingSection):
	"""
	This represents a question that collects a response from users.
	Question Types:
	- Boolean (True or False)
		- Boolean (Yes or No)
		- Short Text (< 255 characters)
		- Long Text (> 255 characters)
		- Integer (-100, 5, 0, 16, etc.)
		- Float (3.1415926, -96.876, etc.)
		- Multiple Choice (Radio Selection)
		- Multiple Choice (Listbox Selection)
		- File Upload

	Options:
		- question - the HTML of the question
		- type - the question type (from the list above)
		- choices - the semicolon (;) separated list of choices for multiple choice questions
		- allowable_extensions - file extensions allowed for file uploads
		- start_date - when answers to this question are first accepted
		- end_date - when answers to this question are no longer accepted

	"""
	QUESTION_TYPES = (
	('BOOL-TF', 'Boolean (True or False)'),
	('BOOL-YN', 'Boolean (Yes or No)'),
	('CHAR', 'Short Text (< 255 characters)'),
	('TEXT', 'Long Text (> 255 characters)'),
	('INT', 'Integer (-100, 5, 0, 16, etc.)'),
	('FLOAT', 'Float (3.1415926, -96.876, etc.)'),
	('CHOICE-RADIO', 'Multiple Choice (Radio Selection)'),
	('CHOICE-BOX', 'Multiple Choice (Listbox Selection)'),
	('UPLOAD','File Upload'),
	)
	question = models.TextField(default="",blank=True,null=True)
	type = models.CharField(max_length=15,default=QUESTION_TYPES[0][0],choices=QUESTION_TYPES)
	choices = models.TextField("Choices",blank=True,null=True,default="",help_text="Enter the choices here separated by semicolons(;).")
	allowable_extensions = models.CharField("Extensions",max_length=255,blank=True,null=True,
		default=".pdf;.doc;.docx;.txt;.rtf",
		help_text="""A semicolon (;) separated list of allowed file extensions in submissions 
		(leave blank to allow any extensions). Example - .pdf;.doc;.docx;.txt;.rtf""")
	start_date = models.DateTimeField("Start Date",blank=True,null=True,help_text="(Optional) After this date & time, answers to this question will be accepted.")
	end_date = models.DateTimeField("End Date",blank=True,null=True,help_text="(Optional) After this date & time, no new answers are accepted (unless 'Allow Late Submissions' is selected for the activity).")

	def save(self, *args, **kwargs):
		self.section_type = "QUESTION"
		self.question = sanitize(self.question)
		self.choices = sanitize(self.choices)
		if self.content_object and not self.section_number:
			self.section_number = self.content_object.questions.count() + 1
		self.title = "Question %s" % self.section_number
		super(ActivityQuestion,self).save(*args, **kwargs)
	def __unicode__(self):
		return self.question if self.question else 'Section %s' % self.section_number
	def get_choices(self,add_blank=False):
		if self.choices:
			items = self.choices.split(';')
			if items[-1] == '': del items[-1]
			if add_blank: items.insert(0,'')
			return zip(range(0,len(items)),items)
		return ('','')
	def get_response(self, user):
		ResponseSet = CreateActivityResponseSet(activity=self.content_object, question_id=self.section_number, user=user)
		if ResponseSet:
			return ResponseSet[0].response
		return None
	def link(self,course):
		try:
			string = course_reverse('apps.activities.views.view_question',course,category=self.content_object.category_slug,title=self.content_object.title_slug,id=self.section_number)
			print "link is %s" % string
			return string
		except Exception as e:
			print "Exception %s" % e
			return ""
	def has_started(self):
		return (self.start_date and self.start_date < datetime.datetime.now()) or not self.start_date
	def has_ended(self):
		return (self.end_date and self.end_date > datetime.datetime.now())
	def get_response_forms(self, users, postinfo=None):
		Formset = {}
		ResponseSet = CreateActivityResponseSet(activity=self)
		for user in users:
			Formset[user] = {}
			for question in ResponseSet.keys():
				Formset[user][question] = {'response':None, 'form':None}
		for question, responses in ResponseSet.items():
			for response in responses:
				if not response.user in Formset: Formset[response.user] = {}
				Formset[response.user][question] = {'response':response, 'form':GradingSectionGradeForm(postinfo,instance=response)}
		#Formset[user][question]['response'] = response
		#Formset[user][question]['form'] = GradingSectionGradeForm
		return Formset
class ActivityQuestionForm(forms.ModelForm):
	class Meta:
		model = ActivityQuestion
		widgets={
			'choices':forms.Textarea(attrs={'class':'mceNoEditor'}),
			'allowable_extensions':forms.TextInput(attrs={'size':'60'})
			}

class ActivityResponse(GradingSectionGrade):
	type = []
	date = models.DateTimeField(auto_now=True)
	submission_number = models.PositiveIntegerField(default=1,blank=True)
	def save(self, *args, **kwargs):
		question = ActivityQuestion.objects.get(pk=self.section.id)
		if question and question.type not in self.type: raise TypeError("Section type %s is of incorrect type for response type %s" % (section.type, self.type))
		self.submission_number = ActivityResponse.objects.filter(user=self.user,section=self.section).count() + 1
		super(ActivityResponse,self).save(*args,**kwargs)
	def get_response(self, question):
		return self.response
	class Meta:
		abstract = True
class ActivityResponseForm(forms.ModelForm):
	class Meta:
		abstract = True
		model = ActivityResponse
		exclude = ['section', 'user', 'date', 'submission_number', 'points', 'notes', 'excused']
class ActivityResponseGradingForm(forms.ModelForm):
	class Meta:
		model = ActivityResponse
		exclude = ['section', 'user', 'date', 'submission_number']

class ActivityResponseBoolean(ActivityResponse):
	type = ['BOOL-TF','BOOL-YN']
	response = models.NullBooleanField("Response",blank=True, null=True)
	def get_response(self, question):
		if self.response is None: return ''
		if question.type == "BOOL-YN": return "Yes" if self.response else "No"
		else: return "True" if self.response else "False"
class ActivityResponseBooleanTFForm(ActivityResponseForm):
	response = forms.TypedChoiceField(label="Response",empty_value=None,required=False,choices=((True,'True'),(False,'False')),widget=forms.RadioSelect)
	class Meta(ActivityResponseForm.Meta):
		model = ActivityResponseBoolean
class ActivityResponseBooleanYNForm(ActivityResponseForm):
	response = forms.TypedChoiceField(label="Response",empty_value=None,required=False,choices=((True,'Yes'),(False,'No')),widget=forms.RadioSelect)
	class Meta(ActivityResponseForm.Meta):
		model = ActivityResponseBoolean

class ActivityResponseInteger(ActivityResponse):
	type = ['INT']
	response = models.IntegerField("Response",blank=True, null=True)
class ActivityResponseIntegerForm(ActivityResponseForm):
	class Meta(ActivityResponseForm.Meta):
		model = ActivityResponseInteger

class ActivityResponseFloat(ActivityResponse):
	type = ['FLOAT']
	response = models.FloatField("Response",blank=True, null=True)
class ActivityResponseFloatForm(ActivityResponseForm):
	class Meta(ActivityResponseForm.Meta):
		model = ActivityResponseFloat

class ActivityResponseShortAnswer(ActivityResponse):
	type = ['CHAR']
	response = models.CharField("Response",max_length=512,blank=True, null=True)
class ActivityResponseShortAnswerForm(ActivityResponseForm):
	class Meta(ActivityResponseForm.Meta):
		model = ActivityResponseShortAnswer

class ActivityResponseLongAnswer(ActivityResponse):
	type = ['TEXT']
	response = models.TextField("Response",blank=True, null=True)
class ActivityResponseLongAnswerForm(ActivityResponseForm):
	class Meta(ActivityResponseForm.Meta):
		model = ActivityResponseLongAnswer

activity_uploads = FileSystemStorage(location=os.path.join(settings.SITE_ROOT,"uploads"),base_url="/uploads/")
class ActivityUploadInput(forms.FileInput):
	initial_text = ugettext_lazy('Currently')
	input_text = ugettext_lazy('Change')

	template_with_initial = u'%(initial_text)s: %(initial)s <br />%(input_text)s: %(input)s'

	def render(self, name, value, attrs=None):
		substitutions = {
			'initial_text': self.initial_text,
			'input_text': self.input_text,
		}
		template = u'%(input)s'
		substitutions['input'] = super(ActivityUploadInput, self).render(name, value, attrs)
		if value and hasattr(value, "url"):
			filename = '.'.join(os.path.basename(value.path).split('.')[2:])
			template = self.template_with_initial
			substitutions['initial'] = (u'<a href="uploads/%s/">%s</a>'
										% (escape(value.instance.id),
										   escape(force_unicode(filename))))
		return mark_safe(template % substitutions)
def get_submission_path(instance, filename):
	filename_pieces = filename.rsplit('.')
	name = filename_pieces[0]
	ext = ''
	if len(filename_pieces) > 1: ext = '.'.join(filename_pieces[1:])
	ret_string = "%s/activities/%s/%s/%s.%s.%s" % (slugify(instance.section.content_object.course),
						slugify(instance.user.username), 
						slugify(instance.section.content_object.category_slug), 
						slugify(instance.user.username),
						slugify(instance.section.content_object.title_slug), 
						slugify(name))
	if ext: ret_string += ".%s" % ext
	return ret_string
class ActivityResponseFileUpload(ActivityResponse):
	type = ['UPLOAD']
	response = models.FileField("Response",upload_to=get_submission_path, storage=activity_uploads, blank=True, null=True)
class ActivityResponseFileUploadForm(ActivityResponseForm):
	def clean_response(self):
		data = self.cleaned_data['response']
		if data and self.instance:
			question = ActivityQuestion.objects.get(pk=self.instance.section.id)
			if question and question.allowable_extensions:
				ext_list = question.allowable_extensions.split(';')
				if not data.name.endswith(tuple(ext_list)):
					raise forms.ValidationError(u"Only files with extensions %s are allowed." % ', '.join(ext_list))
		# Always return the cleaned data, whether you have changed it or
		# not.
		return data
	class Meta(ActivityResponseForm.Meta):
		model = ActivityResponseFileUpload
		widgets = {'response':ActivityUploadInput()}

class ActivityResponseMultipleChoice(ActivityResponse):
	type = ['CHOICE-BOX','CHOICE-RADIO']
	response = models.CharField("Response",max_length=512,blank=True,null=True)
	def get_response(self, question):
		try: return question.choices.split(';')[int(self.response)]
		except: return self.response
	def __init__(self,*args,**kwargs):
		if "question" in kwargs:
			self.response.choices = kwargs["question"].choices
		super(ActivityResponseMultipleChoice,self).__init__(*args,**kwargs)
class ActivityResponseMultipleChoiceRadioForm(ActivityResponseForm):
	response = forms.ChoiceField(label="Response",required=False,widget=forms.RadioSelect)
	def __init__(self, *args, **kwargs):
		if 'choices' in kwargs:
			choices = kwargs['choices']
			del kwargs['choices']
		super(ActivityResponseMultipleChoiceRadioForm,self).__init__(*args, **kwargs)
		if choices:
			self.fields['response'].choices = choices
	class Meta(ActivityResponseForm.Meta):
		model = ActivityResponseMultipleChoice
class ActivityResponseMultipleChoiceBoxForm(ActivityResponseForm):
	response = forms.ChoiceField(label="Response",required=False)
	def __init__(self, *args, **kwargs):
		if 'choices' in kwargs:
			choices = kwargs['choices']
			del kwargs['choices']
		super(ActivityResponseMultipleChoiceBoxForm,self).__init__(*args, **kwargs)
		#if self.instance:
		#    choices = self.instance.poll_question.get_choices()
		if choices:
			self.fields['response'].choices = choices
	class Meta(ActivityResponseForm.Meta):
		model = ActivityResponseMultipleChoice

class ActivityAutogradeResponse(models.Model):
	"""
	This represents a response that is set when a question is created, along with its
	point value and any notes. When autograde is selected, the system will check
	for responses that match the given response and automatically assign the points
	and notes associated with that response.
	"""
	question = models.ForeignKey(ActivityQuestion, default="", editable=False, related_name="autograde_responses")
	response = models.TextField(blank=True,null=True,default="")
	points = models.FloatField(default = 0.0)
	notes = models.TextField(blank=True,null=True)

	def save(self, *args, **kwargs):
		self.response = sanitize(self.response)
		super(ActivityAutogradeResponse,self).save(*args, **kwargs)
class ActivityAutogradeResponseForm(forms.ModelForm):
	class Meta:
		model = ActivityAutogradeResponse
		widgets = {
			'response':forms.TextInput(),
			'points': forms.TextInput(attrs={'size': 10}),
			'notes':forms.TextInput(),
		}
class ActivityResponseFormContainer:
	"""
	This is a wrapper for an ActivityResponseForm that allows for more
	powerful programmatic access for activity creators. This way more info than
	just that which the form contains can be accessed.

	GradingSection properties:
	- title - the title of the question
	- points - the total points of the question
	- question_number - the display number of the question. This will be different
		from original_question_number if randomize_questions is true for the activity
	- original_question_number - the number of the question

	ActivityQuestion properties
	- question - the question HTML content
	- type - the question type
	- choices - the question choices (for multiple choice questions)
	- allowable_extensions - the accepted file extensions (for file uploads)
	- start_date - the start date of the question
	- end_date - the end_date of the question
	- link - a link to the question

	ActivityResponseForm properties
	- submission_number - the current submission number (one more than the
		total submissions submitted to this question)
	- completed_date - the date of the last submission
	- response - this one is a bit complicated:
		- before start date - displays the link to the question
		- between start and end dates - displays the response input widget
		- after end date - show last response
		also, shows additional information if accessed by the instructor
		or assistant
	"""
	def __init__(self, course, user, activity_question, question_number, activity_response_form):
		self.course = course
		self.user = user
		self.has_form = False
		self.activity = activity_question.content_object
		self._question = activity_question
		self.form = activity_response_form
		self._original_question_number = activity_question.section_number
		self._question_number = question_number
	def __unicode__(self):
		if not self._question.has_started():
			return self.link
		elif self._question.has_ended():
			#TODO - return something better here
			return self.response
		else:
			return self.question + "<br>" + self.response

	#GradingSection properties
	@property
	def title(self):
		return self._question.title
	@property
	def points(self):
		return self._question.total_points
	@property
	def question_number(self):
		return self._question_number
	@property
	def original_question_number(self):
		return self._original_question_number

	#ActivityQuestion properties
	@property
	def question(self):
		return self._question.question
	@property
	def type(self):
		return self._question.get_type_display()
	@property
	def choices(self):
		return self._question.choices
	@property
	def allowable_extensions(self):
		return self._question.allowable_extensions
	@property
	def start_date(self):
		return self._question.start_date
	@property
	def end_date(self):
		return self._question.end_date
	@property
	def link(self):
		return "<a href=\"" + self._question.link(self.course) + "\">Question " + str(self.question_number) + "</a>"

	#ActivityResponseForm properties
	@property
	def submission_number(self):
		return self.form.instance.submission_number
	@property
	def completed_date(self):
		return self.form.instance.date
	@property
	def response(self):
		self.has_form = True
		ReturnValue = ""
		submissions_allowed = self.activity.submissions_allowed if self.activity.submissions_allowed > 0 else self.course.default_submissions_allowed
		if (self.submission_number <= submissions_allowed) and (self.activity.allow_late_submissions or (self._question.has_started() and not self._question.has_ended())):
			ReturnValue += "%s %s" % (self.form['response'], self.form['response'].errors)
		else:
			ReturnValue += "%s" % self.form.instance.response
		#if self.course_participant and (self.course_participant.status == "INSTRUCTOR" or self.course_participant.status == "ASSISTANT"):
			#TODO: Add instructor stuff here
		#	pass
		return ReturnValue

class ActivityResponseFormsetContainer(list):
	def has_form(self):
		for item in self:
			if item.has_form: return True
		return False